//eslint-disable: no-console
import Context from '../Context';
import { joinPath, isSysPath, isExternalPath } from '../Utils';

import { sync as resolveSync } from 'resolve';
import * as fs from 'fs';
import VFile = require('vinyl');
import './Types';
import { Dict } from '../types';
import { Buffer } from 'buffer';
import * as path from 'path';
import Loader from '../loaders/Loader';

export default abstract class File {
    public static readonly NODE_MODULES_PATH = '/node_modules/';
    public static CREATORS: Dict<((cx: Context, op: CreateFileOptions) => File)> = {};
    //prepare phase
    public readonly abstract type: FileType;
    public readonly cx: Context;
    public readonly src?: string;
    public readonly updateTime: number;
    // load phase
    public id: string;
    public readonly dependencies: Dict<File> = {};
    public readonly extraFiles: File[] = [];
    public readonly loader?: Loader;
    public _contents: string | Buffer | undefined;
    public extraSourceOptions?: any;

    public status: FileStatus;


    protected constructor(cx: Context, op: CreateFileOptions) {
        this.cx = cx;
        this.status = FileStatus.prepare;
        this.src = op.src;
        if (this.src != null) {
            if (op.updateTime == null) {
                let stat = fs.statSync(this.src);
                this.updateTime = stat.mtimeMs;
            } else {
                this.updateTime = op.updateTime;
            }
            let ext = (path.extname(this.src as string) || '').toLocaleLowerCase();
            this.loader = this.cx.loaders[ext] || this.cx.defaultLoader;
        } else {
            this.updateTime = op.updateTime || 0;
            this.loader = undefined;
        }
        if (op.id != null) {
            this.id = op.id;
        } else if (this.src != null) {
            this.id = joinPath(path.relative(cx.options.rootPath, this.src));
        } else {
            throw new Error('a file must have a src or id');
        }
    }

    public prepare() {
        if (this.loader) {
            this.loader.prepare(this);
        }
    }

    protected async doLoad(): Promise<void> {
        // not implement
    }

    public async load(): Promise<void> {
        if (this.status === FileStatus.prepare) {
            try {
                this.status = FileStatus.loading;
                await this.doLoad();
                this.status = FileStatus.ready;
                if (this.cx.options.printLoad) {
                    console.log('==', this.type, this.id);
                }
            } catch (e) {
                this.status = FileStatus.error;
                console.error('LoadError: ', this.id);
                throw e;
            }
        }
    }

    public static obtain(cx: Context, filePath: string, relativeFrom?: string): File {
        if (isSysPath(filePath) || isExternalPath(filePath, cx.options)) {
            return cx.files[filePath];
        }
        let src = fs.realpathSync(resolveSync(filePath, {
            basedir: relativeFrom || cx.options.rootPath,
            extensions: cx.options.ext,
        })).replace(/\\/g, '/');
        let stat = fs.statSync(src);
        let updateTime = stat.mtimeMs;
        let old = cx.files[src];
        if (old != null && old.updateTime === updateTime) {
            return old;
        }
        let op: CreateFileOptions = { src, updateTime };
        let file: File;
        if (src.includes(this.NODE_MODULES_PATH)) {
            file = cx.files[src] = this.CREATORS[FileType.module](cx, op);
        } else {
            file = cx.files[src] = this.CREATORS[FileType.source](cx, op);
        }
        file.prepare();
        return file;
    }

    public toVinylFile(): VFile | undefined {
        let base = this.cx.options.rootPath;
        let path = this.getVinylFilePath();
        return new VFile({
            cwd: base,
            base,
            path,
            contents: this.rawContents,
        });
    }

    public getVinylFilePath() {
        return joinPath(this.cx.options.rootPath, this.id);
    }

    public loadContents() {
        if (this.src != null && this._contents === undefined) {
            this._contents = fs.readFileSync(this.src);
            if (this.cx.options.printRead) {
                console.log('<<', this.type, this.src);
            }
        }
    }

    get contents() {
        this.loadContents();
        if (this._contents !== undefined) {
            if (typeof this._contents === 'string') {
                return this._contents;
            } else {
                return this._contents = this._contents.toString(this.cx.options.encoding);
            }
        } else {
            throw new Error('load contents fail');
        }
    }

    set contents(s) {
        this._contents = s;
    }

    get rawContents() {
        this.loadContents();
        if (this._contents !== undefined) {
            if (typeof this._contents === 'string') {
                return this._contents = new Buffer(this._contents, this.cx.options.encoding);
            } else {
                return this._contents;
            }
        } else {
            throw new Error('load contents fail');
        }
    }

    set rawContents(b) {
        this._contents = b;
    }

}
